﻿namespace Smart.Navigation.Context
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Smart.Navigation.Descripter;
    using Smart.Navigation.Plugin;

    /// <summary>
    /// コンテキストプラグイン
    /// </summary>
    public class ContextPlugin : INavigatorPlugin
    {
        //--------------------------------------------------------------------------------//
        // メンバ
        //--------------------------------------------------------------------------------//

        #region <メンバ>

        private static readonly Dictionary<string, object> EmptyDictionary = new Dictionary<string, object>();

        private readonly Dictionary<Type, AttributeMember<ViewContextAttribute>[]> cache = new Dictionary<Type, AttributeMember<ViewContextAttribute>[]>();

        #endregion

        //--------------------------------------------------------------------------------//
        // メソッド.プラグイン
        //--------------------------------------------------------------------------------//

        #region <メソッド.プラグイン>

        /// <summary>
        /// 遷移元ビューイベント
        /// </summary>
        /// <param name="context">プラグインコンテキスト</param>
        /// <param name="view">遷移元ビュー</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:パブリック メソッドの引数の検証", Justification = "Called by the framework only")]
        public void OnForwardFrom(PluginContext context, object view)
        {
            context.Save(this, GatherCurrentContext(view));
        }

        /// <summary>
        /// 遷移先ビューイベント
        /// </summary>
        /// <param name="context">プラグインコンテキスト</param>
        /// <param name="view">遷移先ビュー</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:パブリック メソッドの引数の検証", Justification = "Called by the framework only")]
        public void OnForwardTo(PluginContext context, object view)
        {
            var existingContexts = context.Load<Dictionary<string, object>>(this) ?? EmptyDictionary;
            SetupContext(context, view, existingContexts);
        }

        #endregion

        //--------------------------------------------------------------------------------//
        // サポート
        //--------------------------------------------------------------------------------//

        #region <サポート>

        /// <summary>
        /// 属性付きメンバ情報一覧取得
        /// </summary>
        /// <param name="type">型</param>
        /// <returns>属性付きメンバ情報一覧</returns>
        private IEnumerable<AttributeMember<ViewContextAttribute>> GetAttributeMembers(Type type)
        {
            AttributeMember<ViewContextAttribute>[] members;
            if (cache.TryGetValue(type, out members))
            {
                return members;
            }

            members = AttributeMemberFactory.GetAttributeMembers<ViewContextAttribute>(type);
            cache[type] = members;
            return members;
        }

        /// <summary>
        /// コンテキスト抽出
        /// </summary>
        /// <param name="view">遷移元ビュー</param>
        /// <returns>コンテキスト一覧</returns>
        private Dictionary<string, object> GatherCurrentContext(object view)
        {
            var parameters = new Dictionary<string, object>();

            foreach (var member in GetAttributeMembers(view.GetType()))
            {
                var key = member.Attribute.Key ?? member.MemberType.FullName;
                parameters.Add(key, member.GetValue(view));
            }

            return parameters;
        }

        /// <summary>
        /// コンテキスト適用
        /// </summary>
        /// <param name="context">プラグインコンテキスト</param>
        /// <param name="view">遷移先ビュー</param>
        /// <param name="existingContexts">コンテキスト一覧</param>
        private void SetupContext(PluginContext context, object view, Dictionary<string, object> existingContexts)
        {
            var used = new HashSet<string>();

            if (context.ActionType != ActionType.Pop)
            {
                foreach (var member in GetAttributeMembers(view.GetType()))
                {
                    var key = member.Attribute.Key ?? member.MemberType.FullName;
                    var obj = GetContext(member.MemberType, key, existingContexts);
                    member.SetValue(view, obj);
                    used.Add(key);
                }
            }

            if (context.ActionType != ActionType.Push)
            {
                foreach (var support in existingContexts.Keys.Where(key => !used.Contains(key)).Select(key => existingContexts[key]).OfType<IContextSupport>())
                {
                    support.Dispose();
                }
            }
        }

        /// <summary>
        /// コンテキスト取得
        /// </summary>
        /// <param name="type">コンテキスト型</param>
        /// <param name="key">コンテキストキー</param>
        /// <param name="existingContexts">コンテキスト一覧</param>
        /// <returns>コンテキスト</returns>
        private static object GetContext(Type type, string key, IDictionary<string, object> existingContexts)
        {
            object context;
            existingContexts.TryGetValue(key, out context);
            if (context != null)
            {
                return context;
            }

            context = Activator.CreateInstance(type);

            var support = context as IContextSupport;
            if (support != null)
            {
                support.Initilize();
            }

            return context;
        }

        #endregion
    }
}